#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019, Niklas Hauser
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------

import re
from pathlib import Path

# -----------------------------------------------------------------------------
def init(module):
    module.name = ":cmsis:device"

def prepare(module, options):
    device = options[":target"]
    if device.identifier["platform"] != "sam":
        return False

    module.depends(":cmsis:core")

    module.add_query(
        EnvironmentQuery(name="clock-map", factory=clock_map))

    return True

pp = {}

def clock_map_gclk(env, header):
    clock_pattern = re.compile(r"^#define\s+(?P<clk_per>(MCLK|PM))_(?P<clk>(AHB|APB[A-E]))MASK_(?P<per>([A-Z0-9])+(_2X)?(_[A-Z]+)?)_Pos(\s+)(?P<pos>([0-9]+))")
    peripheral_pattern = re.compile(r"^(?P<name>[A-Za-z]+([0-9]+[A-Za-z]+)?(_2X)?(_[A-Z]+)?)(?P<instance>[0-9]?)$")
    clock_map = {}
    with open(header, "r") as clock_header:
        for line in clock_header:
            m = clock_pattern.match(line)
            if m:
                m2 = peripheral_pattern.match(m.group("per"))
                (peripheral, instance) = (m2.group("name"), m2.group("instance"))
                if (peripheral, instance) in clock_map:
                    clock_map[(peripheral, instance)].append((m.group("clk_per"), m.group("clk"), m.group("pos")))
                else:
                    clock_map[(peripheral, instance)] = [(m.group("clk_per"), m.group("clk"), m.group("pos"))]
    return clock_map

def clock_map(env):
    folder = Path(localpath(pp["folder"])) / "component"
    clock_file = None
    if (folder / "mclk.h").exists():
        clock_file = folder / "mclk.h"
    elif (folder / "mclk_100.h").exists():
        clock_file = folder / "mclk_100.h"
    elif (folder / "pm.h").exists():
        clock_file = folder / "pm.h"
    elif (folder / "pm_100.h").exists():
        clock_file = folder / "pm_100.h"

    if clock_file is not None:
        return clock_map_gclk(env, clock_file)
    return {}

def validate(env):
    device = env[":target"]

    # Some families use the variant in header defines, some do not (e.g. SAMG)
    names = [
        "".join([device.identifier[f] for f in ["platform", "series", "pin", "flash", "variant"]]),
        "".join([device.identifier[f] for f in ["platform", "series", "pin", "flash"]]),
    ]

    device_define = None
    family_file = None
    device_header = None
    for famfile in Path(localpath("sam")).glob("**/sam.h"):
        content = famfile.read_text(encoding="utf-8", errors="replace")
        match = re.findall(r"defined\((?P<define>__SAM.*?__)\)", content)
        for n in names:
            define = "__{}__".format(n.upper())
            if match is not None and define in match:
                # In case of multiple matches the most specific header is selected.
                # The one with the matching variant suffix will have the longest name.
                if device_define is not None and len(device_define) > len(define):
                    continue
                family_file = famfile.relative_to(localpath("."))
                device_header = "{}.h".format(n)
                device_define = define


    if family_file is None:
        raise ValidateException("No device define found for '{}'!".format(device.partname))

    family_folder = family_file.parent

    global pp
    pp = {
        "define": device_define,
        "folder": family_folder,
        "device_header": device_header,
    }

def build(env):
    global pp
    env.collect(":build:path.include", "modm/ext")
    env.collect(":build:path.include", "modm/ext/cmsis/device")

    env.outbasepath = "modm/ext/cmsis/device"
    files = ["sam.h", "component-version.h", "pio", "instance", "component", pp["device_header"]]
    for file in files:
        env.copy(localpath(pp["folder"], file), file)

    env.substitutions = {
        "headers": [pp["device_header"]],
        "defines": [pp["define"]],
    }
    env.outbasepath = "modm/src/modm/platform"
    env.template("device.hpp.in")
